// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html

import BezierPath from "../BezierPath";
import RecycleListItem from "./RecycleListItem";
import RecycleListLayout, { HorizontalLayout, VerticalLayout } from "./RecycleListLayout";

const { ccclass, property } = cc._decorator;

@ccclass
export default class BeizerLayout extends RecycleListLayout {
    @property({
        type: BezierPath,
        tooltip: "贝塞尔路径"
    })
    bezierPath: BezierPath = null;

    @property({
        tooltip: "反向布局"
    })
    reverse: boolean = false;

    @property({
        tooltip: "间隔"
    })
    space: number = 0;

    @property({
        tooltip: "起始内边距"
    })
    paddingStart: number = 0;

    @property({
        tooltip: "末尾内边距"
    })
    paddingEnd: number = 0;

    private _itemCount: number = 0;

    private _firstIndex: number = -1;
    private _lastIndex: number = -1;

    private _contentPositionChanged: boolean = false;

    private _contentCount: number = 0;
    private _bezierCount: number = 0;
    private _offset: number = 0;

    public resetLayout() {
        let scrollView = this.recycleList.scrollView;
        if (scrollView.vertical && scrollView.horizontal) return cc.warn("BeizerLayout 不同时支持 vertical 和 horizontal");
        this.bezierPath.updateBezier();

        let tempNode = this.recycleList.tempNode;
        let view = this.recycleList.view;

        this._firstIndex = this._lastIndex = -1;

        this._contentCount = this.recycleList.getDataLength();

        let arcLen = this.bezierPath.getLength();

        if (scrollView.vertical) {
            //贝塞尔曲线可容纳的item数量
            this._bezierCount = this.getLessLengthMaxNum(tempNode.height, this.space, arcLen - this.paddingStart - this.paddingEnd);
            this._itemCount = Math.min(this._contentCount, this._bezierCount);
            scrollView.content.width = this.recycleList.view.width;

            if (this._contentCount === 0 || this._contentCount < this._bezierCount) {
                scrollView.content.height = 0;
            } else {
                scrollView.content.height = (this._contentCount - 1) * (tempNode.height + this.space) + tempNode.height
                    - arcLen + this.paddingStart + this.paddingEnd + view.height;
            }

        } else if (scrollView.horizontal) {
            //贝塞尔曲线可容纳item数量
            this._bezierCount = this.getLessLengthMaxNum(tempNode.width, this.space, arcLen - this.paddingStart - this.paddingEnd);
            this._itemCount = this._itemCount || Math.min(this._contentCount, this._bezierCount);
            scrollView.content.height = this.recycleList.view.width;

            if (this._contentCount === 0 || this._contentCount < this._bezierCount) {
                scrollView.content.width = 0;
            } else {
                scrollView.content.width = (this._contentCount - 1) * (tempNode.width + this.space) + tempNode.width
                    - arcLen + this.paddingStart + this.paddingEnd + view.width;
            }
        }

        this.setContentPosition(cc.Vec2.ZERO);
    }
    public updateLayout() {
        if (this._contentPositionChanged) {
            let recycleList = this.recycleList;
            let itemComp: RecycleListItem, dataIndex: number
            for (let i = 0, len = this._itemCount; i < len; i++) {
                itemComp = recycleList.loadItem(i);
                dataIndex = this.clampItemIndex(i, this._itemCount, this._firstIndex, this._lastIndex);
                if (this.isDataIndexInView(dataIndex)) {
                    if (itemComp.dataIndex !== dataIndex) {
                        this.recycleList.itemDataChanged(itemComp, dataIndex);
                    } else {
                        itemComp.show();
                        itemComp.node.setPosition(this.getItemPositionByDataIndex(itemComp.dataIndex));
                    }
                } else {
                    itemComp.hide();
                }
            }
            this._contentPositionChanged = false;
        }
    }

    public onContentPositionChanged() {
        if (this._itemCount === 0) return;
        let scrollView = this.recycleList.scrollView;
        let itemNode = this.recycleList.tempNode;
        let contentOffset = this.getContentOffset();
        this._offset = this.recycleList.scrollView.vertical ? contentOffset.y : contentOffset.x;
        let step = scrollView.vertical ? itemNode.height : itemNode.width;
        let arcLen = this.bezierPath.getLength();
        let validLen = arcLen - this.paddingStart - this.paddingEnd;
        this._firstIndex = this._offset < 0.5 * step ? 0 : Math.floor((this._offset - 0.5 * step) / (step + this.space)) + 1;
        this._lastIndex = this._offset + validLen < 0.5 * step ? 0 : Math.floor((this._offset + validLen - 0.5 * step) / (step + this.space));
        this._lastIndex = this._lastIndex >= this._contentCount ? this._contentCount - 1 : this._lastIndex;
        // cc.log("index:", this._firstIndex, this._lastIndex);
        this._contentPositionChanged = true;
    }

    public getItemPositionByDataIndex(dataIndex: number): cc.Vec2 {
        let scrollView = this.recycleList.scrollView;
        let itemNode = this.recycleList.tempNode;
        let step = scrollView.vertical ? itemNode.height : itemNode.width;
        let arcLen = this.bezierPath.getLength();
        // cc.log("offset", dataIndex, this._offset);
        let offset = dataIndex * (step + this.space) + 0.5 * step - this._offset + this.paddingStart;
        let viewLocalPos = this.bezierPath.getPointAt(this.reverse ? arcLen - offset % arcLen : offset % arcLen);
        let worldPos = this.recycleList.view.convertToWorldSpaceAR(viewLocalPos);
        return this.recycleList.content.convertToNodeSpaceAR(worldPos);
    }

    public getScrollOffset(dataIndex: number, percent: number): cc.Vec2 {
        let scrollView = this.recycleList.scrollView;
        let itemNode = this.recycleList.tempNode;
        let step = scrollView.vertical ? itemNode.height : itemNode.width;
        let offset = dataIndex * (step + this.space) + 0.5 * step;
        let arcLen = this.bezierPath.getLength();
        let validLen = arcLen - this.paddingStart - this.paddingEnd;

        offset -= percent * validLen;
        // cc.log("scroll offset", offset);
        offset -= this._offset;

        if (this.horizontalLayout === HorizontalLayout.RIGHT_TO_LEFT
            || this.verticalLayout === VerticalLayout.BOTTOM_TO_TOP) {
            offset *= -1;
        }
        let scrollOffset = scrollView.getScrollOffset();

        return scrollView.vertical ? cc.v2(0, scrollOffset.y + offset) : cc.v2(-scrollOffset.x + offset, 0);
    }

    /**
     * 给定指定长度,获取小于指定长度所需步长和间距的最大数量
     * @param step 
     * @param space 
     * @param length 
     * @returns 
     */
    private getLessLengthMaxNum(step: number, space: number, length: number) {
        return 0.5 * step > length ? 0 : Math.floor((length - 0.5 * step) / (step + space)) + 1;
    }

    /**
     * 将index限制在[minIndex,maxIndex]范围
     * @param index 
     * @param step 
     * @param minIndex 
     * @param maxIndex 
     * @returns 
     */
    private clampItemIndex(index: number, step: number, minIndex: number, maxIndex: number): number {
        while (index < minIndex && index + step <= maxIndex) {
            index += step;
        }
        while (index > maxIndex && index - step >= minIndex) {
            index -= step;
        }
        return index;
    }

    public getItemLength(): number {
        return this._itemCount;
    }
    public isDataIndexInView(dataIndex: number): boolean {
        return dataIndex >= this._firstIndex && dataIndex <= this._lastIndex;
    }
    public dataIndex2ItemIndex(dataIndex: number): number {
        return this.clampItemIndex(dataIndex, this._itemCount, 0, this._itemCount - 1);
    }
    public itemIndex2DataIndex(itemIndex: number): number {
        return this.clampItemIndex(itemIndex, this._itemCount, this._firstIndex, this._lastIndex);
    }
}
